#include "color/trivariate.h"
#include <algorithm>

using namespace anip;

class RGBProvider : public TrivariateColorProvider
{
public:
    void toTrueColor(
        const float* buffer, TrueColor* out, size_t n) const override
    {
        TrueColor::from_sRGB((color*)buffer, out, n);
    }

    void fromTrueColor(
        const TrueColor* buffer, float* out, size_t n) const override
    {
        TrueColor::to_sRGB(buffer, (color*)out, n);
    }

    ComponentInfo first() const override {
        return { L"R", 0, 1 };
    };
    ComponentInfo second() const override {
        return { L"G", 0, 1 };
    };
    ComponentInfo third() const override {
        return { L"B", 0, 1 };
    };
};

static float HSVHelper_f(float h, float s, float v, float n)
{
    float k = fmodf(n + h * 6, 6);
    return v - v * s * std::clamp(std::min(k, 4 - k), 0.0f, 1.0f);
}

class HSVProvider : public TrivariateColorProvider
{
public:
    void toTrueColor(
        const float* buffer, TrueColor* out, size_t n) const override
    {
        size_t j = 0, k = 0;
        for (size_t i = 0; i < n; i++)
        {
            float 
                h = buffer[j++],
                s = buffer[j++],
                v = buffer[j++],
                a = buffer[j++];
            ((float*)out)[k++] = HSVHelper_f(h, s, v, 5);
            ((float*)out)[k++] = HSVHelper_f(h, s, v, 3);
            ((float*)out)[k++] = HSVHelper_f(h, s, v, 1);
            ((float*)out)[k++] = a;
        }
        TrueColor::from_sRGB((color*)out, out, n);
    }

    void fromTrueColor(
        const TrueColor* buffer, float* out, size_t n) const override
    {
        TrueColor::to_sRGB(buffer, (color*)out, n);

        size_t j = 0, k = 0;
        for (size_t i = 0; i < n; i++)
        {
            float
                r = out[j++],
                g = out[j++],
                b = out[j++],
                a = out[j++];
            
            float
                v = std::max({ r, g, b }),
                c = v - std::min({ r, g, b }),
                h = 0;
            if (c != 0)
            {
                if (v == r)      h = fmodf((g - b) / (6 * c) + 1, 1);
                else if (v == g) h = (b - r) / (6 * c) + 1.0f / 3;
                else if (v == b) h = (r - g) / (6 * c) + 2.0f / 3;
            }
            out[k++] = h;
            out[k++] = (v == 0) ? 0 : c / v;
            out[k++] = v;
            out[k++] = a;
        }
    }

    ComponentInfo first() const override {
        return { L"H", 0, 1, true, true, true };
    };
    ComponentInfo second() const override {
        return { L"S", 0, 1, true, false };
    };
    ComponentInfo third() const override {
        return { L"V", 0, 1, true, false };
    };
};

class LABProvider : public TrivariateColorProvider
{
public:
    void toTrueColor(
        const float* buffer, TrueColor* out, size_t n) const override
    {
        if ((void*)buffer == (void*)out) return;
        std::copy(buffer, buffer + n * 4, (float*)out);
    }

    void fromTrueColor(
        const TrueColor* buffer, float* out, size_t n) const override
    {
        if ((void*)buffer == (void*)out) return;
        std::copy((const float*)buffer, (const float*)buffer + n * 4, out);
    }

    ComponentInfo first()  const override {
        return { L"L", 0, 100, true, false };
    };
    ComponentInfo second() const override {
        return { L"a*", -128, 128 };
    };
    ComponentInfo third()  const override {
        return { L"b*", -128, 128 };
    };
};

class hCLProvider : public TrivariateColorProvider
{
public:
    void toTrueColor(
        const float* buffer, TrueColor* out, size_t n) const override
    {
        size_t j = 0, k = 0;
        for (size_t i = 0; i < n; i++)
        {
            float
                h = (buffer[j++] - 0.5f) * 3.1416f * 2,
                c = buffer[j++],
                l = buffer[j++],
                a = buffer[j++];
            ((float*)out)[k++] = l;
            ((float*)out)[k++] = cosf(h) * c; // a*
            ((float*)out)[k++] = sinf(h) * c; // b*
            ((float*)out)[k++] = a;
        }
    }

    void fromTrueColor(
        const TrueColor* buffer, float* out, size_t n) const override
    {
        size_t k = 0;
        for (size_t i = 0; i < n; i++)
        {
            TrueColor tc = buffer[i];
            // I believe atan2f(0, 0) should give 0, is that true?
            ((float*)out)[k++] = atan2f(tc.b, tc.a) / (3.1416f * 2) + 0.5f; // h
            ((float*)out)[k++] = sqrtf(tc.a * tc.a + tc.b * tc.b); // C
            ((float*)out)[k++] = tc.L; // L
            ((float*)out)[k++] = tc.A;
        }
    }

    ComponentInfo first()  const override {
        return { L"h", 0, 1, true, true, true };
    };
    ComponentInfo second() const override {
        return { L"C", 0, 125, true, false };
    };
    ComponentInfo third()  const override {
        return { L"L", 0, 100, true, false };
    };
};

class hSLProvider : public TrivariateColorProvider
{
public:
    void toTrueColor(
        const float* buffer, TrueColor* out, size_t n) const override
    {
        size_t j = 0, k = 0;
        for (size_t i = 0; i < n; i++)
        {
            float
                h = (buffer[j++] - 0.5f) * 3.1416f * 2,
                s = buffer[j++],
                l = buffer[j++],
                a = buffer[j++];
            ((float*)out)[k++] = l;
            ((float*)out)[k++] = cosf(h) * l * s; // a*
            ((float*)out)[k++] = sinf(h) * l * s; // b*
            ((float*)out)[k++] = a;
        }
    }

    void fromTrueColor(
        const TrueColor* buffer, float* out, size_t n) const override
    {
        size_t k = 0;
        for (size_t i = 0; i < n; i++)
        {
            TrueColor tc = buffer[i];
            float s = 0, h = 0;
            if (tc.L > 0.01f) // a rather arbitrary epsilon value?
            {
                h = atan2f(tc.b, tc.a) / (3.1416f * 2) + 0.5f;
                s = sqrtf(tc.a * tc.a + tc.b * tc.b) / tc.L;
            }
            ((float*)out)[k++] = h;
            ((float*)out)[k++] = s;
            ((float*)out)[k++] = tc.L;
            ((float*)out)[k++] = tc.A;
        }
    }

    ComponentInfo first()  const override {
        return { L"h", 0, 1, true, true, true };
    };
    ComponentInfo second() const override {
        return { L"S", 0, 3, true, false };
    };
    ComponentInfo third()  const override {
        return { L"L", 0, 100, true, false };
    };
};

const TrivariateColorProvider
    * TrivariateColorProvider::sRGB = new RGBProvider(),
    * TrivariateColorProvider::HSV = new HSVProvider(),
    * TrivariateColorProvider::CIELAB = new LABProvider(),
    * TrivariateColorProvider::CIEhLC = new hCLProvider(),
    * TrivariateColorProvider::CIEhLS = new hSLProvider();